package org.example.distributedid;

/**
 * 实现雪花算法生成分布式ID
 * 雪花算法的特点是 ：1、64位
 * 第一位 ：0/1 标识位 固定为0
 * 1-41 ： 时间戳
 * 42-47 ：数据中心id
 * 48-52 ：机器id
 * 53-64:  序列号
 *
 * 需求：
 * 所有生成的id按时间趋势递增
 * 整个分布式系统内不会产生重复id（因为有datacenterId和workerId来做区分）
 *
 */
public class SnowFlake {
    private long datacenterId;
    private long machineId;
    private long sequence;
    private long lastTimestamp = -1L;
    public SnowFlake(long datacenterId, long machineId,long sequence) {
        this.datacenterId = datacenterId;
        this.machineId = machineId;
        this.sequence = sequence;
    }
    private int machineIdShift = 5;
    private long datacenterIdShift = 5;
    private long sequenceShift = 12;


    //边界考虑 。各个值的允许最大值
    // -1 的二进制位全1   左移x位表示 后x位为0 再与-1 异或即后x位均为1表示其最大值
    private long maxMachineId = ~(-1 << machineIdShift);
    private long maxDatacenterId = ~(-1 << datacenterIdShift);
    private long maxSequence = ~(-1 << sequenceShift);

    //序列号
    //机器ID 左移12位
    //数据中心id左移 17位
    //时间戳 左移 22 位
    //最后结果 ｜
    public  long generateId() {
        long snowFlakeId;
        //todo前置的值校验 如传入的值是否预期内
        long currentTimeStamp = System.currentTimeMillis();
        if (currentTimeStamp < lastTimestamp) {
            //时间戳获取出现问题
            System.out.println("Something went wrong when get current time");
            throw new RuntimeException("Something went wrong when get current time");
        }
        //同一ms的请求 进行序列号加一
        if (currentTimeStamp == lastTimestamp) {
            //循环加一 加一的同时保证了在max范围内
            sequence = (sequence + 1) & maxSequence;
            //代表已经超过1ms内可支持生成的id最大值了，则需要等待到下个ms
            if (sequence == 0) {
                long tmp = System.currentTimeMillis();
                while (tmp == lastTimestamp) {
                    tmp = System.currentTimeMillis();
                }
                currentTimeStamp = tmp;
            }
        }else
            sequence = 0;
        lastTimestamp = currentTimeStamp;
        //如果时间戳相同则序列号加一
        snowFlakeId = currentTimeStamp << 22 | datacenterId << 17 | machineId << 12 |
                 sequence ;
        return snowFlakeId;
    }

    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(1, 1, 1);
        for (int i = 0; i < 10; i++) {
            System.out.println(snowFlake.generateId());

        }

    }
}
